﻿using System;
using System.Collections.Generic;
using System.Collections;

namespace SharpTag.ID3
{
    /// <summary>
    /// ShartpTag.ID3 Extension methods.
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// Calculates the SyncSafe size of an ID3 Tag and/or ID3v2 Frame.
        /// </summary>
        /// <param name="sizeBytes">A byte array containing the size data of an <see cref="ITag"/> or <see cref="IFrame"/> object.</param>
        /// <returns>The long size value.</returns>
        public static long CalcualteSyncSafeSize(this byte[] sizeBytes)
        {
            long returnSize = 0;
            double exp = 0.0;
            BitArray sizeArray = new BitArray(sizeBytes);

            for (int pos = 24; pos < 31; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 16; pos < 23; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 8; pos < 15; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 0; pos < 7; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            return returnSize;
        }

        /// <summary>
        /// Calculates the non SyncSafe size of an ID3 Tag and/or ID3v2 Frame.
        /// </summary>
        /// <param name="sizeBytes">A byte array containing the size data of an <see cref="ITag"/> or <see cref="IFrame"/> object.</param>
        /// <returns>The long size value.</returns>
        public static long CalculateSize(this byte[] sizeBytes)
        {
            long returnSize = 0;
            double exp = 0.0;
            BitArray sizeArray = new BitArray(sizeBytes);

            for (int pos = 24; pos < 32; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 16; pos < 24; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 8; pos < 16; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            for (int pos = 0; pos < 8; pos++)
            {
                if (sizeArray[pos])
                    returnSize += (long)Math.Pow(2.0, exp);

                exp++;
            }

            return returnSize;
        }

        /// <summary>
        /// Finds an instance position of a byte in a byte array.
        /// </summary>
        /// <param name="originalByteArray">A byte array containing the bytes to be searched.</param>
        /// <param name="byteToFind">The byte to be found.</param>
        /// <param name="instanceNumberToFind">The instance number of the byte to be found.</param>
        /// <returns>The int position of the found instance byte.</returns>
        public static int FindPosition(this byte[] originalByteArray, byte byteToFind, int instanceNumberToFind)
        {
            var returnPos = 0;
            var currentInstance = 0;

            for (int i = 0; (i < originalByteArray.Length) && (currentInstance < instanceNumberToFind); i++)
            {
                if (originalByteArray[i] == byteToFind)
                {
                    returnPos = i;
                    currentInstance++;
                }
            }

            if (currentInstance == instanceNumberToFind)
                return returnPos;

            return -1;
        }

        /// <summary>
        /// Parses a raw frame byte array into the individual data fields.
        /// </summary>
        /// <param name="originalByteArray">The raw frame byte array.</param>
        /// <param name="definition">A <see cref="IFrameDefinition"/> object containing the parsing details of the particular <see cref="IFrame"/>.</param>
        /// <returns>A generic list of byte arrays.</returns>
        public static List<byte[]> ParseFrameBytes(this byte[] originalByteArray, IFrameDefinition definition)
        {
            byte[] tempByteArray;
            byte[] workingByteArray = new byte[originalByteArray.Length];
            var workingPos = 0;
            List<byte[]> returnList = new List<byte[]>();
            Array.Copy(originalByteArray, 0, workingByteArray, 0, originalByteArray.Length);
            foreach (var delim in definition.FrameDelimiters)
            {
                switch (delim.Value)
                {
                    case FrameDelimiter.ZeroedByteString:
                    case FrameDelimiter.ZeroedByte:
                        int pos = workingByteArray.FindPosition(0, 1);
                        workingPos += pos;
                        tempByteArray = new byte[pos + 1];
                        Array.Copy(workingByteArray, 0, tempByteArray, 0, tempByteArray.Length);
                        returnList.Add(tempByteArray);
                        workingByteArray = new byte[workingByteArray.Length - tempByteArray.Length];
                        Array.Copy(originalByteArray, workingPos, workingByteArray, 0, workingByteArray.Length);
                        break;
                    case FrameDelimiter.OneByte:
                        tempByteArray = new byte[1];
                        tempByteArray[0] = workingByteArray[workingPos];
                        returnList.Add(tempByteArray);
                        workingPos++;
                        workingByteArray = new byte[workingByteArray.Length - 1];
                        Array.Copy(originalByteArray, workingPos, workingByteArray, 0, workingByteArray.Length);
                        break;
                    case FrameDelimiter.TwoBytes:
                        tempByteArray = new byte[2];
                        tempByteArray[0] = workingByteArray[workingPos];
                        tempByteArray[1] = workingByteArray[workingPos + 1];
                        returnList.Add(tempByteArray);
                        workingPos += 2;
                        workingByteArray = new byte[workingByteArray.Length - 2];
                        Array.Copy(originalByteArray, workingPos, workingByteArray, 0, workingByteArray.Length);
                        break;
                    case FrameDelimiter.ThreeBytes:
                        tempByteArray = new byte[3];
                        tempByteArray[0] = workingByteArray[workingPos];
                        tempByteArray[1] = workingByteArray[workingPos + 1];
                        tempByteArray[2] = workingByteArray[workingPos + 2];
                        returnList.Add(tempByteArray);
                        workingPos += 3;
                        workingByteArray = new byte[workingByteArray.Length - 3];
                        Array.Copy(originalByteArray, workingPos, workingByteArray, 0, workingByteArray.Length);
                        break;
                    case FrameDelimiter.FourBytes:
                        tempByteArray = new byte[4];
                        tempByteArray[0] = workingByteArray[workingPos];
                        tempByteArray[1] = workingByteArray[workingPos + 1];
                        tempByteArray[2] = workingByteArray[workingPos + 2];
                        tempByteArray[3] = workingByteArray[workingPos + 3];
                        returnList.Add(tempByteArray);
                        workingPos += 4;
                        workingByteArray = new byte[workingByteArray.Length - 4];
                        Array.Copy(originalByteArray, workingPos, workingByteArray, 0, workingByteArray.Length);
                        break;
                    case FrameDelimiter.EndOfFrameString:
                    case FrameDelimiter.EndOfFrame:
                        returnList.Add(workingByteArray);
                        break;

                }
            }

            return returnList;
        }

        /// <summary>
        /// Gets the string representation of a subset of a byte array.
        /// </summary>
        /// <param name="originalByteArray">A byte array containing all the data.</param>
        /// <param name="offset">The offset to start the string at.</param>
        /// <param name="length">The length of the string to create.</param>
        /// <returns>A string representation of the subset of byte values.</returns>
        public static string GetOffsetValue(this byte[] originalByteArray, long offset, long length)
        {
            char[] workingField = new char[length];
            Array.Copy(originalByteArray, offset, workingField, 0, length);
            return new string(workingField);
        }

        /// <summary>
        /// Adjusts a string to a specified length by either trimming or padding it with a specified character.
        /// </summary>
        /// <param name="originalString">The original string value.</param>
        /// <param name="size">The target length of the final string.</param>
        /// <param name="paddingChar">Padding character used to lengthen a string.</param>
        /// <returns>The adjusted string.</returns>
        public static string SetToLength(this string originalString, int size, char paddingChar)
        {
            if (originalString.Length > size)
                return originalString.Substring(0, size);
            else
            {
                var returnString = originalString.PadRight(size, paddingChar);
                return returnString;
            }
        }
    }
}
